/*
 * JFugue - API for Music Programming
 * Copyright (C) 2003-2008  David Koelle
 *
 * http://www.jfugue.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

package org.jfugue;

import java.util.HashMap;
import java.util.Map;

/**
 * Provides the ability to build a rhythm using a simple strings in which
 * individual characters represent MusicString elements.
 *
 * For example, you can develop a drum beat that looks like this: <code>
 * oo'' o'  oo'' o'  oo'' o'  oo'' o'...
 * </code>
 *
 * As of JFugue 4.0, the Rhythm allows the addition of voices outside of the
 * percussion track using the addVoice() method.
 *
 * This feature is covered in detail in "The Complete Guide to JFugue"
 *
 * @see Player
 * @author David Koelle
 * @version 3.0
 * @version 4.0.3 - Now implements Serializable
 */
public class Rhythm {
	private Map<Character, String> charToNote;
	private String[] layers;
	private String[] voices;
	private String[] voiceDetails;
	private int MAX_LAYERS = 127;
	private int MAX_VOICES = 16;
	private int PERCUSSION_TRACK = 9;

	public Rhythm() {
		charToNote = new HashMap<Character, String>();
		layers = new String[MAX_LAYERS];
		voices = new String[MAX_VOICES];
		voiceDetails = new String[MAX_VOICES];
	}

	public void addSubstitution(char stringChar, String musicString) {
		charToNote.put(stringChar, musicString);
	}

	public String getSubstitution(char stringChar) {
		return charToNote.get(stringChar);
	}

	public void removeSubstitution(char stringChar) {
		charToNote.remove(stringChar);
	}

	public void setLayer(int layer, String rhythmString) {
		if ((layer < 0) || (layer > MAX_LAYERS)) {
			throw new JFugueException(JFugueException.LAYER_EXC,
					Integer.toString(layer), rhythmString);
		}
		layers[layer] = rhythmString;
	}

	public String getLayer(int layer) {
		return this.layers[layer];
	}

	public void clearLayer(int layer) {
		this.layers[layer] = null;
	}

	public void setVoice(int voice, String rhythmString) {
		if ((voice < 0) || (voice > MAX_LAYERS)
				|| (voice == PERCUSSION_TRACK)) {
			throw new JFugueException(JFugueException.VOICE_EXC,
					Integer.toString(voice), rhythmString);
		}
		voices[voice] = rhythmString;
	}

	public String getVoice(int voice) {
		return this.voices[voice];
	}

	public void clearVoice(int voice) {
		this.voices[voice] = null;
	}

	public void setVoiceDetails(int voice, String musicString) {
		if ((voice < 0) || (voice > MAX_LAYERS)
				|| (voice == PERCUSSION_TRACK)) {
			throw new JFugueException(JFugueException.VOICE_EXC,
					Integer.toString(voice), musicString);
		}
		voiceDetails[voice] = musicString;
	}

	public String getVoiceDetails(int voice) {
		return this.voiceDetails[voice];
	}

	public void clearVoiceDetails(int voice) {
		this.voiceDetails[voice] = null;
	}

	public String getMusicString() {
		StringBuffer buffy = new StringBuffer();

		// Start by adding the percussion track
		buffy.append("V9 ");
		for (int i = 0; i < MAX_LAYERS; i++) {
			String rhythmString = getLayer(i);
			if (rhythmString != null) {
				buffy.append("L");
				buffy.append(i);
				buffy.append(" ");
				for (int r = 0; r < rhythmString.length(); r++) {
					char ch = rhythmString.charAt(r);
					String substitution = getSubstitution(ch);
					if (substitution != null) {
						buffy.append(substitution);
						buffy.append(" ");
					}
				}
			}
		}

		// Add the voices
		for (int i = 0; i < MAX_VOICES; i++) {
			boolean voiceCommandAdded = false;

			String detailsString = getVoiceDetails(i);
			if (detailsString != null) {
				buffy.append("V");
				buffy.append(i);
				voiceCommandAdded = true;
				buffy.append(" ");
				buffy.append(detailsString);
				buffy.append(" ");
			}
			String rhythmString = getVoice(i);
			if (rhythmString != null) {
				if (!voiceCommandAdded) {
					buffy.append("V");
					buffy.append(i);
					buffy.append(" ");
				}
				for (int r = 0; r < rhythmString.length(); r++) {
					char ch = rhythmString.charAt(r);
					String substitution = getSubstitution(ch);
					if (substitution != null) {
						buffy.append(substitution);
						buffy.append(" ");
					}
				}
			}
		}

		return buffy.toString();
	}

	public Pattern getPattern() {
		return new Pattern(getMusicString());
	}

	public Pattern getPatternWithInterval(Pattern pattern) {
		IntervalNotation interval = new IntervalNotation(getMusicString());
		return interval.getPatternForRootNote(pattern);
	}

	public Pattern getPatternWithInterval(String string) {
		IntervalNotation interval = new IntervalNotation(getMusicString());
		return interval.getPatternForRootNote(string);
	}

	public Pattern getPatternWithInterval(Note rootNote) {
		IntervalNotation interval = new IntervalNotation(getMusicString());
		return interval.getPatternForRootNote(rootNote);
	}

}
